﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel;

using Seven.Tools;
using Seven.Tools.Collections.Concurrent;

namespace Seven.Pool.SignalR
{
    /// <summary>
    /// 表示SignalR服务的 Token 对象。
    /// </summary>
    public class MemberToken : IEquatable<MemberToken>, IEquatable<Guid>, IEquatable<string>, INotifyPropertyChanged
    {
        private string _userName;
        private Guid _userPoolGuid;
        private string _groupName;
        private Guid? _guid;

        private ConnectionModel _user;
        private MemberTokenCollection _tokensPool;
        private static MemberTokenCollection _tokens;

        private DateTime? _firstConnectTime;
        private DateTime? _lastConnectTime;

        private string _GuidName = "Guid";

        #region 构造函数定义

        private MemberToken(Func<MemberTokenCollection> tokensPoolSelector)
        {
            Check.NotNull(tokensPoolSelector);
            this._tokensPool = tokensPoolSelector();
        }

        private MemberToken(string userName, Guid userPoolToken)
            : this(GetTokensPool)
        {
            this._userName = userName != null ? userName.Trim() : string.Empty;
            this._userPoolGuid = userPoolToken;
            this.InitUser();
        }

        private MemberToken(string userName, Guid userPoolToken, string groupName)
            : this(GetTokensPool)
        {
            this._userName = userName != null ? userName.Trim() : string.Empty;
            this._userPoolGuid = userPoolToken;
            this._groupName = groupName != null ? groupName.Trim() : string.Empty;
            this.InitUser();
        }

        #endregion

        #region internal 静态属性定义 - 获取连接用户列表

        /// <summary>
        /// 获取连接用户列表。
        /// </summary>
        internal static MemberTokenCollection Tokens
        {
            get
            {
                if (_tokens == null)
                { _tokens = new MemberTokenCollection(); }

                return _tokens;
            }
        }

        #endregion

        #region 公共属性定义 - 用户基本信息

        /// <summary>
        /// 获取该连接用户在用户池中的TokenGuid。如果当前连接身份无效，则返回 Guid.Empty。
        /// </summary>
        public Guid UserPoolGuid
        {
            get { return this._user != null ? this._user.UserToken : Guid.Empty; }
        }

        /// <summary>
        /// 获取该连接用户的用户名。如果当前连接身份无效，则返回 null。
        /// </summary>
        public string UserName
        {
            get { return this._user != null ? this._user.UserName : null; }
        }

        /// <summary>
        /// 获取该连接用户的分组名称。如果当前连接身份无效，则返回 null。
        /// </summary>
        public string GroupName
        {
            get { return this._user != null ? this._user.GroupName : null; }
        }

        #endregion

        #region 公共属性定义 - 连接身份标识信息

        /// <summary>
        /// 获取当前连接身份标识对象用于存储连接用户列表的对象池。
        /// </summary>
        internal MemberTokenCollection TokensPool
        {
            get { return this._tokensPool; }
        }

        /// <summary>
        /// 获取当前 <see cref="MemberToken"/> 对象的 Guid 值，表示该连接身份的唯一标识。
        /// </summary>
        public Guid Guid
        {
            get
            {
                if (!this._guid.HasValue)
                { this._guid = Guid.NewGuid(); }

                return this._guid.Value;
            }
            internal set { this._guid = value; this.OnPropertyChanged(this._GuidName); }
        }

        /// <summary>
        /// 获取表示当前连接身份的用户模型对象。
        /// <para>如果当前连接身份无效，则返回 null。</para>
        /// </summary>
        internal ConnectionModel User
        {
            get { return this._user; }
        }

        #endregion

        #region 公共属性定义 - 连接状态相关属性

        /// <summary>
        /// 获取一个 System.Nullable&lt;DateTime&gt; 类型值，表示该连接用户的连接时间。
        /// <para>如果该属性没有返回值，则表示该身份尚未连接。</para>
        /// </summary>
        public DateTime? FirstConnectTime
        {
            get { return this._firstConnectTime; }
            internal set
            {
                this._firstConnectTime = value;
            }
        }

        /// <summary>
        /// 获取一个 System.Nullable&lt;DateTime&gt; 类型值，表示该连接用户最后一次连接到服务器的时间。
        /// <para>如果该属性没有返回值，则表示该连接标识所标识的用户还未连接到服务器。</para>
        /// </summary>
        public DateTime? LastRequestTime
        {
            get { return this._lastConnectTime; }
            internal set
            {
                this._lastConnectTime = value;
            }
        }

        #endregion

        #region 私有方法定义 - 初始化基础对象

        private void InitUser()
        {
            Check.NotEmpty(this._userName);
            this._user = new ConnectionModel { UserName = this._userName, UserToken = this._userPoolGuid, GroupName = this._groupName };
        }

        #endregion

        #region 私有方法定义 - 添加连接、更新连接和移除连接操作的内部方法

        private bool InternalAddConnection(Guid signalRGuid)
        {
            this.Guid = signalRGuid;
            this.TokensPool.Add(this.Guid, this);

            this.FirstConnectTime = DateTime.Now;

            return true;
        }

        private bool InternalUpdateConnection(Guid signalRGuid)
        {
            KeyValuePair<Guid,MemberToken> remove = this.TokensPool.FirstOrDefault(f => f.Value.Guid == this.Guid);
            MemberToken token = null;
            if (this.TokensPool.TryRemove(remove.Key, out token))
            {
                token.Guid = signalRGuid;
                token.FirstConnectTime = DateTime.Now;
                token.LastRequestTime = null;

                this.TokensPool.Add(token.Guid, token);

                return true;
            }

            return false;
        }

        private bool InternalRemoveConnection()
        {
            this.TokensPool.RemoveByGuid(this.Guid);
            return true;
        }

        #endregion

        #region internal 静态方法定义 - 添加连接和移除连接操作

        /// <summary>
        /// 连接服务的核心 API 之一；以指定的用户名、用户池token标识、分组名和SignalRID进行添加连接操作。
        /// <para>若指定的用户池token标识，已存在于连接池，则用 <see cref="signalRGuid"/> 更新旧的 Guid 。</para>
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <param name="userPoolGuid">用户池token标识</param>
        /// <param name="groupName">分组名</param>
        /// <param name="signalRGuid">SignalRID</param>
        /// <returns></returns>
        internal static bool AddConnection(string userName, Guid userPoolGuid, string groupName, Guid signalRGuid)
        {
            MemberToken token = GetTokenByUserPoolGuid(userPoolGuid);
            if (token != null)
            {
                return token.InternalUpdateConnection(signalRGuid);
            }
            else
            {
                token = new MemberToken(userName, userPoolGuid, groupName);
                return token.InternalAddConnection(signalRGuid);
            }
        }

        /// <summary>
        /// 连接服务的核心 API 之一；对指定的用户名执行移除连接操作
        /// <para>该用户名在所有设备上的连接都会被移除。</para>
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <param name="groupName">分组名</param>
        /// <returns></returns>
        internal static IEnumerable<KeyValuePair<Guid,bool>> RemoveConnection(string userName, string groupName)
        {
            MemberToken[] tokens = GetTokenByUserName(userName, groupName);
            if (tokens.Length == 0) { return Enumerable.Empty<KeyValuePair<Guid, bool>>().ToArray(); }

            return tokens.Select(s => new KeyValuePair<Guid,bool>(s.Guid, s.InternalRemoveConnection())).ToArray();
        }

        /// <summary>
        /// 连接服务的核心 API 之一；对指定的连接身份的标识进行移除连接操作。
        /// <para>该连接身份的标识对应的用户的连接会被移除。</para>
        /// </summary>
        /// <param name="tokenGuid">连接身份的标识</param>
        /// <returns></returns>
        internal static bool RemoveConnection(Guid tokenGuid)
        {
            MemberToken token = GetTokenByGuid(tokenGuid);
            if (token == null) { return false; }

            return token.InternalRemoveConnection();
        }

        #endregion

        #region internal 静态方法定义 - 获取连接状态相关信息

        /// <summary>
        /// 获取用于存储连接用户列表的对象池。
        /// </summary>
        /// <returns></returns>
        internal static MemberTokenCollection GetTokensPool()
        {
            return Tokens;
        }

        /// <summary>
        /// 获取在指定的 <see cref="groupName"/> 中指定的 <see cref="userName"/> 的所有连接身份信息。
        /// <para>如果该方法返回一个空数组，则说明该用户无效或者尚未连接。</para>
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <param name="groupName">分组名</param>
        /// <returns></returns>
        public static MemberToken[] GetTokenByUserName(string userName, string groupName)
        {
            return Tokens.GetTokenByUserName(userName, groupName);
        }

        /// <summary>
        /// 根据指定的连接身份信息的 Token KEY 值获取该连接身份信息。
        /// <para>如果该方法返回 null，则说明该 <paramref name="token"/> 无效。</para>
        /// </summary>
        /// <param name="token">字符串格式的连接身份信息的 Token KEY 值</param>
        /// <returns></returns>
        public static MemberToken GetTokenByGuid(string key)
        {
            return Tokens.GetTokenByGuid(key);
        }

        /// <summary>
        /// 根据指定的连接身份信息的 Token KEY 值获取该连接身份信息。
        /// <para>如果该方法返回 null，则说明该 <paramref name="token"/> 无效。</para>
        /// </summary>
        /// <param name="token">连接身份信息的 Token KEY 值</param>
        /// <returns></returns>
        public static MemberToken GetTokenByGuid(Guid key)
        {
            return Tokens.GetTokenByGuid(key);
        }

        /// <summary>
        /// 根据指定的用户池中的身份token标识获取该token标识在连接池中对应的连接身份信息。
        /// <para>如果该方法返回 null，则说明该 <paramref name="key"/> 无效 或 对应的用户尚未连接。</para>
        /// </summary>
        /// <param name="key">用户池中的身份token标识</param>
        /// <returns></returns>
        public static MemberToken GetTokenByUserPoolGuid(Guid key)
        {
            return Tokens.GetTokenByUserPoolGuid(key);
        }

        #endregion

        #region INotifyPropertyChanged 接口实现

        /// <summary>
        /// 引发 PropertyChanged 事件。
        /// </summary>
        public virtual void OnPropertyChanged()
        {
            this.OnPropertyChanged(this._GuidName);
        }

        /// <summary>
        /// 引发带有提供参数的 PropertyChanged 事件。
        /// </summary>
        /// <param name="propertyName"></param>
        public virtual void OnPropertyChanged(string propertyName)
        {
            if (string.IsNullOrWhiteSpace(propertyName)) { return; }

            if (this.PropertyChanged != null)
            {
                PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
                this.PropertyChanged(this, e);
            }
        }

        /// <summary>
        /// 当 Guid 属性值变更时触发动作。
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region 运算符重载

        /// <summary>
        /// 判断两个 <see cref="MemberToken"/> 对象是否表示相同的登录身份标识。
        /// </summary>
        /// <param name="item1">要比较的Token</param>
        /// <param name="item2">要比较的Token</param>
        /// <returns></returns>
        public static bool operator ==(MemberToken item1, MemberToken item2)
        {
            if (Object.ReferenceEquals(item1, null))
            { return Object.ReferenceEquals(item2, null); }

            return item1.Equals(item2);
        }

        /// <summary>
        /// 判断两个 <see cref="MemberToken"/> 对象是否表示不同的登录身份标识。
        /// </summary>
        /// <param name="item1">要比较的Token</param>
        /// <param name="item2">要比较的Token</param>
        /// <returns></returns>
        public static bool operator !=(MemberToken item1, MemberToken item2)
        {
            return !(item1 == item2);
        }

        public override bool Equals(object obj)
        {
            if (Object.ReferenceEquals(obj, null)) { return false; }

            if (obj is MemberToken)
            { return this.Equals(obj as MemberToken); }
            else if (obj is string)
            { return this.Equals(obj as string); }
            else if (obj is Guid)
            { return this.Equals((Guid)obj); }
            else
            { return base.Equals(obj); }
        }

        public bool Equals(MemberToken other)
        {
            if (Object.ReferenceEquals(this, other)) { return true; }
            if (Object.ReferenceEquals(other, null)) { return false; }

            return this.Equals(other.Guid) && this.Equals(other.UserName);
        }

        public bool Equals(string other)
        {
            if (string.IsNullOrWhiteSpace(other)) { return false; }

            return this.UserName == other;
        }

        public bool Equals(Guid other)
        {
            return this.Guid == other;
        }

        /// <summary>
        /// 返回此实例的哈希代码，重写自 Object.GetHashCode。
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Guid.GetHashCode();
        }

        #endregion
    }
}